package pl.pw.mkmw.pnganimator.file;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import pl.pw.mkmw.pnganimator.exception.PngAnimatorException;
import pl.pw.mkmw.pnganimator.image.DataDecompressor;
import pl.pw.mkmw.pnganimator.image.SImage;
import pl.pw.mkmw.pnganimator.image.chunks.ChunkType;
import pl.pw.mkmw.pnganimator.image.chunks.FileSignature;
import pl.pw.mkmw.pnganimator.image.filter.Filter;

/**
 * A class for file loading.
 *
 * @author MK
 */
public class FileLoader {

    private static final Logger LOG = Logger.getLogger(FileLoader.class.getName());

    
    /**
     * This method loads normal image, like png, bmp, gif and so on (See ImageIO spec).
     * Use it to load casual images.
     * 
     * @param uri - uri of image to load.
     * @return
     * @throws IOException 
     */
    public SImage loadImage(URI uri) throws IOException {
        SImage simg = null;
        BufferedImage img = null;
        Color[][] table;
        try {
            img = ImageIO.read(new File(uri));
            table = new Color[img.getHeight()][img.getWidth()];

            for (int x = 0; x < img.getWidth(); x++) {
                for (int y = 0; y < img.getHeight(); y++) {
                    table[y][x] = new Color(img.getRGB(x, y));

                }
            }
            simg = new SImage(table);
            return simg;
        } catch (IOException e) {
            throw e;
        }
    }
    private InputStream is;
    private byte[] currentChunkName;
    private byte[] currentChunkData;
    private byte[] currentChunkCRC;

    /**
     * 
     * This methods loads animated image (like xpng) created by this software.
     */
    public List<SImage> loadAnimatedImage(URI uri) throws IOException, PngAnimatorException {
        List<SImage> animation = new ArrayList<SImage>();

        try {
            is = new FileInputStream(new File(uri));
            currentChunkName = new byte[]{'v', 'o', 'i', 'd'};

            if (!hasValidSignature()) {
                throw new PngAnimatorException("Problem while checking file signature");
            }

            //IHDR
            while (!(new String(currentChunkName).equals("IHDR"))) {
                readNextChunk();
            }

            int width = getWidthFromIHDR();
            LOG.log(Level.INFO, "Got Image width = {0}", width);
            int height = getHeightFromIHDR();
            LOG.log(Level.INFO, "Got Image height = {0}", height);

            //omit rest IHDR data, we only save in TRUECOLOR_ALPHA so, whatever.
            while (!(Arrays.equals(currentChunkName, ChunkType.IDAT))) {
                readNextChunk();
            }

            byte[] uncompressedFrame = DataDecompressor.decompress(currentChunkData);

            SImage first = getSImageFromBytes(uncompressedFrame, width, height);

            animation.add(first);

            while (!(Arrays.equals(currentChunkName, ChunkType.IEND))) {
                readNextChunk();
                if ((Arrays.equals(currentChunkName, ChunkType.xDAT))) {
                    uncompressedFrame = DataDecompressor.decompress(currentChunkData);
                    animation.add(getSImageFromBytes(uncompressedFrame, width, height));
                }
            }
        } catch (IOException e) {
            throw e;
        } finally {
            closeStream();
        }

        //defilter next frames;
        Filter defilter = new Filter();

        animation = defilter.defilterSimpleFuture(animation);

        return animation;


    }

    private boolean hasValidSignature() {
        LOG.log(Level.INFO, "Checking signature");
        try {
            byte[] validSignature = FileSignature.getSig();

            for (int i = 0; i < validSignature.length; i++) {
                if (validSignature[i] != (byte) is.read()) {
                    return false;
                }
            }
            return true;
        } catch (IOException ex) {
            Logger.getLogger(FileLoader.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }

    private void readNextChunk() throws PngAnimatorException {
        LOG.log(Level.INFO, "Reading next chunk");
        int length;
        try {
            //chunk length
            byte[] fourbytes = new byte[4];
            is.read(fourbytes, 0, 4);
            length = ByteBuffer.wrap(fourbytes).getInt();
            //chunk name
            is.read(currentChunkName, 0, 4);
            //read chunk data
            currentChunkData = new byte[length];
            is.read(currentChunkData, 0, length);
            currentChunkCRC = new byte[4];
            is.read(currentChunkCRC, 0, 4);
            //crc check?
            LOG.log(Level.INFO, "Read {0} chunk with {1} length", new Object[]{new String(currentChunkName), length});
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
            throw new PngAnimatorException("IOException while reading file");
        }
    }

    private int getWidthFromIHDR() {
        int width;

        byte[] fourbytes = Arrays.copyOfRange(currentChunkData, 0, 4);
        width = ByteBuffer.wrap(fourbytes).getInt();
        return width;
    }

    private int getHeightFromIHDR() throws PngAnimatorException {
        int height;

        byte[] fourbytes = Arrays.copyOfRange(currentChunkData, 4, 8);
        height = ByteBuffer.wrap(fourbytes).getInt();
        return height;
    }

    /**
     * Converts bytes into SImage of given width and height.
     * 
     * 
     * @param uncompressedFirstFrame - uncompressed byte array representing image
     * @param width - image width
     * @param height - image heigth
     * @return
     * @throws PngAnimatorException 
     */
    private SImage getSImageFromBytes(byte[] uncompressedFirstFrame, int width, int height) throws PngAnimatorException {
        if (uncompressedFirstFrame.length != (((width * 4) + 1) * height)) {
            throw new PngAnimatorException("Image width/height inconsistent. IHDR said " + width + "x" + height + "=" + ((width * 4 + 1) * height) + ", in IDAT we found " + uncompressedFirstFrame.length);
        }

        int[][] red = new int[height][width];
        int[][] green = new int[height][width];
        int[][] blue = new int[height][width];
        int[][] alpha = new int[height][width];

        int index = 0;

        for (int y = 0; y < height; y++) {
            //omit filter byte
            index++;
            for (int x = 0; x < width; x++) {
                red[y][x] = uncompressedFirstFrame[index++];
                green[y][x] = uncompressedFirstFrame[index++];
                blue[y][x] = uncompressedFirstFrame[index++];
                alpha[y][x] = uncompressedFirstFrame[index++];
            }
        }

        SImage simg = new SImage(red, green, blue, alpha);
        return simg;
    }

    private void closeStream() throws IOException {
        is.close();
    }
}
